/*
 * Atomic Predicates for Transformers
 * 
 * Copyright (c) 2015 UNIVERSITY OF TEXAS AUSTIN. All rights reserved. Developed
 * by: HONGKUN YANG and SIMON S. LAM http://www.cs.utexas.edu/users/lam/NRL/
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * with the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimers.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimers in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. Neither the name of the UNIVERSITY OF TEXAS AUSTIN nor the names of the
 * developers may be used to endorse or promote products derived from this
 * Software without specific prior written permission.
 * 
 * 4. Any report or paper describing results derived from using any part of this
 * Software must cite the following publication of the developers: Hongkun Yang
 * and Simon S. Lam, Scalable Verification of Networks With Packet Transformers
 * Using Atomic Predicates, IEEE/ACM Transactions on Networking, October 2017,
 * Volume 25, No. 5, pages 2900-2915 (first published as IEEE Early Access
 * Article, July 2017, Digital Object Identifier: 10.1109/TNET.2017.2720172).
 * 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH
 * THE SOFTWARE.
 */
package transformer;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import common.BDDACLWrapper;
import common.ForwardingRule;
import common.PositionTuple;



public class NetworkCOVE extends Network{

    /**
     * 
     */
    private static final long serialVersionUID = -7495132087061957898L;

    public static final String outsideInterface = "outside";
    public static final String nullInterface = "null";

    public NetworkCOVE(String foldername) throws IOException
    {
        this.name = foldername;

        boxes = new HashMap<String, Box>();

        BuildNetwork.set_al2s(false);

        bddengine = new BDDACLWrapper();
        Box.setBDDWrapper(bddengine);

        MPLSTable.set_bdd_engine(bddengine);
        IPinIP.set_bdd_engine(bddengine);

        Device.has_v6 = false;

        File folder = new File(foldername);
        File[] listFiles = folder.listFiles();

        HashSet<Long> used_nat_addrs = new HashSet<Long>();
        HashSet<String> box_in_network = new HashSet<String>();
        for(File file : listFiles)
        {
            String filename = file.getName();
            if(filename.endsWith("fwd"))
            {
                box_in_network.add(filename.split("\\.")[0]);
            }
        }
        System.out.println(box_in_network.size()+" boxes");

        for(File file :  listFiles)
        {
            String filename = file.getName();
            if(filename.endsWith("fwd"))
            {
                String[] tokens = filename.split("\\.");
                Device d = BuildNetwork.parseDevice(tokens[0], file);
                System.out.println(d.getName());

                /**
                 * change forwarding tables
                 */
                for(ForwardingRule r: d.fws)
                {
                    if(!box_in_network.contains(r.getiname()) && !r.getiname().equals(nullInterface))
                    {
                        r.setInterface(outsideInterface);
                    }
                }

                //long t1 = System.nanoTime();
                d.computeFWBDDs();
                //long t2 = System.nanoTime();
                //System.out.println(d.name + " computing bdds takes " + (t2 - t1) + "ns");

                if(d.find_nat_subnet_minlen("null", 30, used_nat_addrs))
                {
                    used_nat_addrs.add(d.nat_addr);
                }else
                {
                    System.err.println("cannot find a good nat address for "+d.name);
                    //System.exit(-1);
                }
                d.fws = null;

                //these prefixes are used for nat and other purposes
                d.fwbdds.remove("null");
                //

                boxes.put(d.getName(), d);
            }
        }            

        System.out.println(BuildNetwork.num_fw4 + " forwarding rules.");

        //find topology from forwarding tables
        topology = new HashMap<PositionTuple, HashSet<PositionTuple>> ();


        for(Box d : boxes.values())
        {
            Set<String> ports = ((Device) d).fwbdds.keySet();
            for(String port : ports)
            {
                if(boxes.containsKey(port))
                {
                    addTopology(d.getName(), port, port, d.getName());
                }
            }
        }


        setactiveports();
        edgerouters = new ArrayList<String> ();


    }

    public NetworkCOVE(String foldername, String edgeroutersfile) throws IOException
    {
        this(foldername);
        if(edgeroutersfile != null)
        {
            try  {
                BufferedReader br = new BufferedReader(new FileReader(new File(edgeroutersfile)));
                String line;
                while ((line = br.readLine()) != null) {
                    String edge = line.trim();
                    if(edge.length() > 0)
                    {
                        edgerouters.add(edge);
                    }
                }
                br.close();
            }catch (Exception e)
            {
                e.printStackTrace();
            }
        }else
        {
            for(String dname : boxes.keySet())
            {
                if(((Device) boxes.get(dname)).has_valid_nataddr())
                {
                    edgerouters.add(dname);

                }
            }

        }

        System.out.println(edgerouters.size()+ " edge routers");

    }

    

    

    public void remove_null_routes()
    {
        for(Box box : boxes.values())
        {
            ((Device) box).fwbdds.remove("null");
        }
    }



    public void save_object() throws IOException
    {
        FileOutputStream fileOut =
                new FileOutputStream(name+".ser");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        out.writeObject(this);
        out.close();
        fileOut.close();
        System.out.println("Serialized data is saved in "+ name+".ser");
    }

    public static NetworkCOVE read_object(String filename) throws IOException, ClassNotFoundException
    {
        FileInputStream fileIn = new FileInputStream(filename);
        ObjectInputStream in = new ObjectInputStream(fileIn);
        NetworkCOVE net =  (NetworkCOVE) in.readObject();
        in.close();

        Box.setBDDWrapper(net.bddengine);
        Device.has_v6 = false;
        Device.only_v6 = false;
        MPLSTable.set_bdd_engine(net.bddengine);
        IPinIP.set_bdd_engine(net.bddengine);

        return net;
    }


    public static void main (String[] args) throws Exception
    {
        long t1 = System.currentTimeMillis();
        NetworkCOVE net = new NetworkCOVE("52941", null);
        long t2 = System.currentTimeMillis();
        
        System.out.println("preprocessing takes " + (t2-t1) + " ms.");
        //net.save_object();

        /*
            long t1 = System.nanoTime();
            net = NetworkCOVE.read_object(args[2]);
            long t2 = System.nanoTime();

            System.out.println("takes " + (t2-t1)/1000000.0+" ms");
         */
        System.out.println(net.boxes.size() + " boxes");
        System.out.println(net.topology.size() + " ports");
        System.out.println(net.edgerouters.size()+" edge routers");

    }



}
